/*
 * Copyright (c) 2007 The University of Reading
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University of Reading, nor the names of the
 *    authors or contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package uk.ac.rdg.resc.wms2kml;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dom4j.Document;
import org.dom4j.io.SAXReader;
import org.simpleframework.xml.load.Persister;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;

/**
 * A Controller that handles the conversion of Capabilities documents
 * to KML wrappers.
 *
 * @author Jon Blower
 * $Revision$
 * $Date$
 * $Log$
 */
public class Cap2KMLController extends MultiActionController
{
    /**
     * Name for the parameter that will contain the Capabilities
     * URL to download
     */
    public static final String CAP_URL = "capurl";
    
    /**
     * Loads the capabilities document into the user's session, then forwards
     * to the options page to allow the user to select the layers for inclusion
     * in the KML.
     */
    public ModelAndView loadCapabilities(
        HttpServletRequest request,
        HttpServletResponse response)
        throws Exception // TODO remove
    {
        try
        {
            String capUrlStr = request.getParameter(CAP_URL);
            if (capUrlStr == null)
            {
                throw new Exception("Must specify the URL to the Capabilities document with "
                    + CAP_URL + "=<url>");
            }
            try
            {
                URL capUrl = new URL(capUrlStr);
                InputStream in = capUrl.openStream();
                
                // Read the XML into memory then we can try parsing it with
                // different classes.
                Reader reader = new InputStreamReader(in);
                char[] cbuf = new char[1024];
                StringBuffer buf = new StringBuffer();
                int n;
                while ((n = reader.read(cbuf)) >= 0)
                {
                    buf.append(cbuf, 0, n);
                }
                reader.close();
                String content = buf.toString().trim();
                
                // Parse the XML using dom4j to find the root element so we know
                // what version to use.
                // The "false" means "don't do validation"
                SAXReader saxReader = new SAXReader(false);
                // Prevent downloading of any DTD
                saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
                Document doc = saxReader.read(new StringReader(content));
                String version = doc.getRootElement().attribute("version").getStringValue().trim();
                
                // Parse the XML using the Simple XML library (http://simple.sf.net)
                WMSCapabilities cap = null;
                if (version.equals("1.3.0"))
                {
                    cap = new Persister().read(WMS1_3Capabilities.class, content);
                }
                else if (version.equals("1.1.1"))
                {
                    cap = new Persister().read(WMS1_1_1Capabilities.class, content);
                }
                else
                {
                    throw new Exception("The WMS Capabilities version " + version
                        + " is not supported");
                }
                request.getSession().setAttribute("capabilities", cap);
                
                response.sendRedirect("../options.jsp");
                return null;
            }
            catch(MalformedURLException mue)
            {
                throw new Exception("Invalid URL to the Capabilities document");
            }
            catch(IOException ioe)
            {
                throw new Exception("Error reading from Capabilities document", ioe);
            }
        }
        catch(Exception e)
        {
            throw e; // TODO: display as XML or HTML
        }
    }
    
    /**
     * Converts the Capabilities document in the Session object to KML 
     * and outputs to the client
     */
    public ModelAndView generateKML(
        HttpServletRequest request,
        HttpServletResponse response)
        throws Exception // TODO remove.  Report exceptions in the KML?
    {
        Object capObj = request.getSession().getAttribute("capabilities");
        String layers = request.getParameter("layers");
        if (capObj == null || layers == null)
        {
            response.sendRedirect(".."); // redirect to default page
            return null;
        }
        WMSCapabilities cap = (WMSCapabilities)capObj;
        
        // Recursively set the setIncludeInKml to false
        resetIncludeInKml(cap.getRootLayer());
        
        // Set the layers that are to be included in the KML
        for (String layerName : layers.split(","))
        {
            Layer layer = cap.findLayerByName(layerName);
            if (layer != null)
            {
                // recursively include this layer and its parents in the KML
                includeLayerAndParentsInKml(layer);
            }
        }
        
        Map<String, Object> models = new HashMap<String, Object>();
        models.put("capabilities", cap);
        String requestURL = request.getRequestURL().toString();
        // Find the base URL by removing the last part of the path.
        // This removes the trailing slash.
        models.put("baseURL", requestURL.substring(0, requestURL.lastIndexOf("/")));

        return new ModelAndView("topLevelKML", models);
    }
    
    public ModelAndView createSubRegion(
        HttpServletRequest request,
        HttpServletResponse response)
        throws Exception // TODO remove.  Report exceptions in the KML?
    {
        // We need to separate out the DBOX argument from the rest of the URL
        StringBuffer wmsArgs = new StringBuffer();
        String wmsBaseUrl = null;
        double[] dbox = null;
        for (Object paramNameObj : request.getParameterMap().keySet())
        {
            String paramName = (String)paramNameObj;
            if (paramName.equals("DBOX"))
            {
                String[] dboxEls = request.getParameter(paramName).split(",");
                if (dboxEls.length != 4)
                {
                    throw new Exception("Invalid DBOX format");
                }
                try
                {
                    dbox = new double[] {
                        Double.parseDouble(dboxEls[0]),
                        Double.parseDouble(dboxEls[1]),
                        Double.parseDouble(dboxEls[2]),
                        Double.parseDouble(dboxEls[3])
                    };
                }
                catch(NumberFormatException nfe)
                {
                    throw new Exception("Invalid DBOX format");
                }
            }
            else if (paramName.equals("URL"))
            {
                wmsBaseUrl = request.getParameter("URL");
            }
            else
            {
                wmsArgs.append(paramName + "=" + request.getParameter(paramName) + "&amp;");
            }
        }
        if (dbox == null)
        {
            throw new Exception("DBOX parameter missing");
        }
        else if (wmsBaseUrl == null)
        {
            throw new Exception("URL parameter missing");
        }
        
        // Calculate the bounding boxes of all sub-regions
        // See http://www.jasonbirch.com/wms2kml/wms2kml.phps
        double[] xvals = new double[]{
            dbox[0],
            dbox[0] - (dbox[0] - dbox[2]) / 2.0,
            dbox[2]
        };
        
        double[] yvals = new double[]{
            dbox[1],
            dbox[1] - (dbox[1] - dbox[3]) / 2.0,
            dbox[3]
        };
        
        // The bounding boxes of all four sub-regions
        LatLonBoundingBox[] regionDBoxes = new LatLonBoundingBox[]{
            new LatLonBoundingBox(xvals[0], yvals[0], xvals[1], yvals[1]),
            new LatLonBoundingBox(xvals[0], yvals[1], xvals[1], yvals[2]),
            new LatLonBoundingBox(xvals[1], yvals[0], xvals[2], yvals[1]),
            new LatLonBoundingBox(xvals[1], yvals[1], xvals[2], yvals[2])
        };
        
        Map<String, Object> models = new HashMap<String, Object>();
        // N.B. we don't add a ? here becuase the wmsBaseUrl already contains
        // the first URL parameter (TODO: this is ugly!)
        models.put("wmsURL", wmsBaseUrl + "&amp;" + wmsArgs.toString());
        models.put("callbackURL", request.getRequestURL().toString());
        models.put("dbox", dbox);
        models.put("regionDboxes", regionDBoxes);
        return new ModelAndView("regionBasedOverlay", models);
    }
    
    /**
     * Recursive method to reset the includeInKml property of all layers
     */
    private static void resetIncludeInKml(Layer layer)
    {
        layer.setIncludeInKml(false);
        for (Layer child : layer.getChildren())
        {
            resetIncludeInKml(child);
        }
    }
    
    /**
     * Recursive method to include a layer and all its parents in the KML
     */
    private static void includeLayerAndParentsInKml(Layer layer)
    {
        if (layer != null)
        {
            layer.setIncludeInKml(true);
            includeLayerAndParentsInKml(layer.getParent());
        }
    }
}
